using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Noein.Control;
using Noein.Physics;
using Noein.Physics.Collision;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;

namespace Noein
{
    public class Object
    {
        private Visual visual;
        public Visual Visual
        {
            get { return visual; }
            set { visual = value; }
        }
        
        private Spatial spatial;
        public Spatial Spatial
        {
            get { return spatial; }
            set { spatial = value; }
        }

        private Physical physical;
        public Physical Physical
        {
            get { return physical; }
            set { physical = value; }
        }

        private BoundingVolume bv;
        public BoundingVolume BoundingVolume
        {
            get { return bv; }
            set { bv = value; }
        }

        private List<Controller> controllers = new List<Controller>();
        public List<Controller> Controllers
        {
            get { return controllers; }
            set { controllers = value; }
        }

        public virtual void Update(Device device, float dt)
        {
            foreach (Controller controller in controllers)
            {
                controller.Update(this, dt);
            }
        }

        public virtual void Render(Device device)
        {
            if (visual.Visible)
            {
                spatial.SetTransform(device);
                visual.Render(device);
            }
        }
    }

    public class ParticleList : Object
    {
        public class Particle : Object
        {
            private float age;
            public float Age
            {
                get { return age; }
                set { age = value; }
            }

            private float lifeTime;
            public float LifeTime
            {
                get { return lifeTime; }
                set { lifeTime = value; }
            }

            public override void Render(Device device)
            {
                // do nothing, render is implemented in outer class
            }
        }

        private LinkedList<Particle> particles = new LinkedList<Particle>();
        public LinkedList<Particle> Particles
        {
            get { return particles; }
            set { particles = value; }
        }

        private int activeCount = 0;

        public int ActiveCount
        {
            get { return activeCount; }
            set { activeCount = value; }
        }
        private float timeSinceLastSpawn = 0;

        public float TimeSinceLastSpawn
        {
            get { return timeSinceLastSpawn; }
            set { timeSinceLastSpawn = value; }
        }

        public override void Update(Device device, float dt)
        {
            base.Update(device, dt);

            // update all living particles
            foreach (Particle particle in particles)
            {
                if (particle.Age < particle.LifeTime)
                {
                    particle.Update(device, dt);
                }
                else
                {
                    break; // short circuit
                }
            }
        }

        public override void Render(Device device)
        {
            if (Visual.Visible)
            {
                foreach (Particle particle in particles)
                {
                    // render all live particles
                    if (particle.Age < particle.LifeTime)
                    {
                        // convert particle from emitter space to world space
                        //device.Transform.World = Matrix.Multiply(Spatial.Transform, particle.Spatial.Transform);

                        particle.Spatial.SetTransform(device);
                        // all particle share same visual
                        Visual.Render(device);
                    }
                }
            }
        }
    }

    public class SpringMass : Object
    {
        private List<Object> masses = new List<Object>();
        private List<SpringForce> springs = new List<SpringForce>();
        private List<MuscleForce> muscles = new List<MuscleForce>();

        public List<MuscleForce> Muscles
        {
            get { return muscles; }
            set { muscles = value; }
        }
       
        public List<ScriptController> scs = new List<ScriptController>();
        private Control.ParticleController pc = new ParticleController();

        private Visual massVisual;

        
        private float floorHeight = -4f;
        private Vector3 loa = new Vector3(0, 1, 0);

        public float ks;
        public float kd;
        public float km;

        public bool move = false;

        public SpringMass(string srcFile, Visual mass, float ks, float kd, float km, bool gravity)
        {

            this.kd = kd;
            this.ks = ks;
            this.km = km;

            ReadFile(srcFile);
            massVisual = mass;

            if (gravity)
                pc.AddForce(Physics.EarthSurfaceGravity.Instance());
            pc.AddForce(new Friction(2f, 2f));


        }

        public override void Render(Device device)
        {
            //base.Render(device);
            foreach (Object mass in masses)
            {
                device.Transform.World = Matrix.Multiply(mass.Spatial.Transform, Spatial.Transform);
                massVisual.Render(device);
            }

            // render springs
            device.Transform.World = Matrix.Identity;
            foreach (SpringForce sf in springs)
            {

                VertexBuffer vb = 
                    new VertexBuffer(typeof(CustomVertex.PositionColored), 2, device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

                CustomVertex.PositionColored[] data =
                    (CustomVertex.PositionColored[])vb.Lock(0, typeof(CustomVertex.PositionColored), LockFlags.None, 50);

                Vector3 obj1Pos = Vector3.TransformCoordinate(sf.obj1.Spatial.Position, Spatial.Transform);

                data[0].X = obj1Pos.X;
                data[0].Y = obj1Pos.Y;
                data[0].Z = obj1Pos.Z;
                data[0].Color = Color.FromArgb(255, 255, 255).ToArgb();

                Vector3 obj2Pos = Vector3.TransformCoordinate(sf.obj2.Spatial.Position, Spatial.Transform);

                data[1].X = obj2Pos.X;
                data[1].Y = obj2Pos.Y;
                data[1].Z = obj2Pos.Z;
                data[1].Color = Color.FromArgb(255, 255, 255).ToArgb();

                vb.Unlock();

                device.SetStreamSource(0, vb, 0);
                device.VertexFormat = CustomVertex.PositionColored.Format;
                device.DrawPrimitives(PrimitiveType.LineList, 0, 1);

                vb.Dispose();
            }

            // render muscles
            foreach (MuscleForce mf in muscles)
            {

                VertexBuffer vb =
                    new VertexBuffer(typeof(CustomVertex.PositionColored), 2, device, Usage.None, CustomVertex.PositionColored.Format, Pool.Managed);

                CustomVertex.PositionColored[] data =
                    (CustomVertex.PositionColored[])vb.Lock(0, typeof(CustomVertex.PositionColored), LockFlags.None, 50);

                Vector3 obj1Pos = Vector3.TransformCoordinate(mf.obj1.Spatial.Position, Spatial.Transform);

                data[0].X = obj1Pos.X;
                data[0].Y = obj1Pos.Y;
                data[0].Z = obj1Pos.Z;
                data[0].Color = Color.FromArgb(255, 255, 255).ToArgb();

                Vector3 obj2Pos = Vector3.TransformCoordinate(mf.obj2.Spatial.Position, Spatial.Transform);

                data[1].X = obj2Pos.X;
                data[1].Y = obj2Pos.Y;
                data[1].Z = obj2Pos.Z;
                data[1].Color = Color.FromArgb(255, 255, 255).ToArgb();

                vb.Unlock();

                device.SetStreamSource(0, vb, 0);
                device.VertexFormat = CustomVertex.PositionColored.Format;
                device.DrawPrimitives(PrimitiveType.LineList, 0, 1);

                vb.Dispose();
            }
        }

        public override void Update(Device device, float dt)
        {
            base.Update(device, dt);

            foreach (Object obj in masses)
            {
                pc.Update(obj, dt);
            }


            // HACK hardcode floor collision
            foreach (Object mass in masses)
            {
                if (mass.Spatial.Position.Y < floorHeight)
                {
                    mass.Physical.contact = loa;

                    mass.Spatial.CorrectTo(new Vector3(mass.Spatial.Position.X, floorHeight - .1f, mass.Spatial.Position.Z));

                    Object floor = new Object();
                    floor.Spatial = new Spatial();
                    floor.Physical = new Physical(floor.Spatial);
                    floor.Physical.Mass = mass.Physical.Mass;

                    Physics.Functions.ApplyImpulse(mass, floor, new Vector3(0, 1, 0), .5f);
                }
                else
                {
                    mass.Physical.contact = Vector3.Empty;
                }

            }


            if (move)
            {
                foreach (ScriptController sc in scs)
                {
                    sc.Update(this, dt);
                    //sc.Update(this, dt);
                    //    foreach (Object mass in masses)
                    //    {
                    //        sc.Update(mass, dt);
                    //    }
                }
            }
        }

        private void ReadFile(string srcFile)
        {
            if (!File.Exists(@"..\..\" + srcFile))
            {
                throw new Exception("spring mass input file not found!");
            }

            using (StreamReader sr = File.OpenText(@"..\..\" + srcFile))
            {
                string line = sr.ReadLine();
                if (line == null)
                    throw new Exception("spring mass input file is empty!");

                Regex o = new Regex(@"[\s]+");

                // read mass
                int massCount = Convert.ToInt32(line);
                for (int i = 0; i < massCount; i++)
                {
                    line = sr.ReadLine();
                    string[] entries = o.Split(line);

                    Object obj = new Object();
                    obj.Spatial = new Spatial();
                    obj.Spatial.TeleportTo(new Vector3(
                        (float)Convert.ToDouble(entries[0]),
                        (float)Convert.ToDouble(entries[1]),
                        (float)Convert.ToDouble(entries[2])));
                    obj.Physical = new Physical(obj.Spatial);

                    masses.Add(obj);
                }

                // read spring
                line = sr.ReadLine();
                int springCount = Convert.ToInt32(line);
                for (int i = 0; i < springCount; i++)
                {
                    line = sr.ReadLine();
                    string[] entries = o.Split(line);

                    Object obj1 = masses[Convert.ToInt32(entries[0])];
                    Object obj2 = masses[Convert.ToInt32(entries[1])];
                    float length = (obj1.Spatial.Position - obj2.Spatial.Position).Length();

                    SpringForce sf = new SpringForce(obj1, obj2, ks, kd, length); 

                    springs.Add(sf);
                    pc.AddForce(sf);
                }

                // read muscle
                line = sr.ReadLine();
                int muscleCount = Convert.ToInt32(line);

                float f = 0f;

                for (int i = 0; i < muscleCount; i++)
                {
                    line = sr.ReadLine();
                    string[] entries = o.Split(line);

                    Object obj1 = masses[Convert.ToInt32(entries[0])];
                    Object obj2 = masses[Convert.ToInt32(entries[1])];
                    MuscleForce mf = new MuscleForce(obj1, obj2, kd);

                    muscles.Add(mf);
                    pc.AddForce(mf);

                    f += 0.7853f;
                    f %= 3.1415f;

                    string formula = String.Format("(set (x (* {0} (sin (+ (* t 2) {1})))))", km, f);
                    string variable = String.Format("Muscles[{0}].Scalar", i);
                    ScriptController sc = new ScriptController(formula, variable);
                    sc.Pause = false;

                    scs.Add(sc);
                }

                sr.Close();
            }
        }
    }

    // TODO
    // public class Composite : Object
}
